﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Threading;

namespace Xceed.Silverlight.Data
{
  internal sealed class DataSource : IDataEnumerable<DataPath>
  {
    #region Static Fields

    private const int DefaultCacheSize = 30;
    private const int SystemDefaultItemCount = 1000;
    private static readonly TimeSpan DefaultRequestExecutionDelay = TimeSpan.FromMilliseconds( 25 );

    #endregion

    #region Constructor

    internal DataSource( Dispatcher dispatcher, DataSourceProvider dataSourceProvider )
    {
      if( dispatcher == null )
        throw new ArgumentNullException( "dispatcher" );

      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      m_dispatcher = dispatcher;
      m_dataSourceProvider = dataSourceProvider;
      m_dataSourceProvider.Init( dispatcher );
      m_dataSourceProvider.CheckMinimumRequirements();

      bool isAsynchronousDataSource = m_dataSourceProvider.IsAsynchronous;

      m_throttleHandler.RequestExecutionDelay = ( isAsynchronousDataSource )
                                                  ? DataSource.DefaultRequestExecutionDelay
                                                  : TimeSpan.Zero;

      m_filterHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( this.InternalSourceChanged );
      m_sortHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( this.InternalSourceChanged );
      m_groupHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( this.InternalSourceChanged );
      m_groupHandler.ExpansionChanged += new EventHandler<ExpansionChangedEventArgs>( this.GroupCollapseChanged );

      m_dataSourceProviderDataHelper = new DataSourceProviderDataHelper( m_dataSourceProvider );
      m_dataPathDataHelper = new DataPathDataHelper<object>( m_dataSourceProviderDataHelper );
      m_groupDataHelper = new GroupDataHelper( new DataPathDataHelper( m_dataPathDataHelper ) );
      m_bufferDataHelper = new BufferDataHelper( m_groupDataHelper, m_groupHandler, m_sortHandler, m_filterHandler, m_dataSourceProvider.PrimaryKeyInfo );
      m_reactiveDataHelper = new ReactiveDataHelper( m_bufferDataHelper, m_groupHandler, m_sortHandler, m_filterHandler, m_dataSourceProvider.PrimaryKeyInfo );
      m_dataSourceDataHelper = new DataSourceDataHelper( m_reactiveDataHelper );

      if( dataSourceProvider.CanCount )
      {
        if( !isAsynchronousDataSource )
        {
          m_groupHandler.GroupCountPolicy = GroupCountPolicy.Query;
          //m_groupHandler.IncludeGroupItems = true;
          m_groupHandler.IncludeGroupItemsCount = true;
        }
        else
        {
          m_groupHandler.GroupCountPolicy = GroupCountPolicy.CacheAsEstimates;
        }
      }
    }

    #endregion

    #region GroupHeadersFootersSelector

    public IGroupHeadersFootersSelector GroupHeadersFootersSelector
    {
      get
      {
        SafeGroupHeadersFootersSelector selector = m_groupHandler.GroupHeadersFootersSelector as SafeGroupHeadersFootersSelector;

        if( selector != null )
          return selector.UnsafeSelector;

        return null;
      }
      set
      {
        SafeGroupHeadersFootersSelector selector = m_groupHandler.GroupHeadersFootersSelector as SafeGroupHeadersFootersSelector;

        if( ( selector == null ) || ( selector.UnsafeSelector != value ) )
        {
          if( value != null )
          {
            m_groupHandler.GroupHeadersFootersSelector = new SafeGroupHeadersFootersSelector( value, m_dispatcher );
          }
          else
          {
            m_groupHandler.GroupHeadersFootersSelector = null;
          }
        }
      }
    }

    #endregion

    #region GroupStatFunctionsSelector

    public IGroupStatFunctionsSelector GroupStatFunctionsSelector
    {
      get
      {
        SafeGroupStatFunctionsSelector selector = m_groupHandler.GroupStatFunctionsSelector as SafeGroupStatFunctionsSelector;

        if( selector != null )
          return selector.UnsafeSelector;

        return null;
      }
      set
      {
        SafeGroupStatFunctionsSelector selector = m_groupHandler.GroupStatFunctionsSelector as SafeGroupStatFunctionsSelector;

        if( ( selector == null ) || ( selector.UnsafeSelector != value ) )
        {
          if( value != null )
          {
            m_groupHandler.GroupStatFunctionsSelector = new SafeGroupStatFunctionsSelector( value, m_dispatcher );
          }
          else
          {
            m_groupHandler.GroupStatFunctionsSelector = null;
          }
        }
      }
    }

    #endregion

    #region ElementType Property

    internal Type ElementType
    {
      get
      {
        return m_dataSourceProvider.ElementType;
      }
    }

    #endregion

    #region DataHelper Property

    internal IDataHelper DataHelper
    {
      get
      {
        return m_dataSourceDataHelper;
      }
    }

    #endregion

    #region GroupHelper Property

    internal IGroupDataHelper GroupHelper
    {
      get
      {
        return m_reactiveDataHelper;
      }
    }

    #endregion

    #region UserObjectConverter

    internal Func<object, DataItem> UserObjectConverter
    {
      get
      {
        if( m_userObjectConverter == null )
        {
          m_userObjectConverter = ( userObject ) => m_dataSourceProviderDataHelper.Convert( null, userObject );
        }

        return m_userObjectConverter;
      }
    }

    private Func<object, DataItem> m_userObjectConverter = null;

    #endregion

    #region Filter Property

    internal Predicate<object> Filter
    {
      get
      {
        return m_filterHandler.Filter;
      }
      set
      {
        m_filterHandler.Filter = value;
      }
    }

    #endregion

    #region FilterExpression Property

    internal FilterExpressionBase FilterExpression
    {
      get
      {
        return m_filterHandler.FilterExpression;
      }
      set
      {
        m_filterHandler.FilterExpression = value;
      }
    }

    #endregion

    #region SortDescriptions Property

    internal SortDescriptionCollection SortDescriptions
    {
      get
      {
        return m_sortHandler.SortDescriptions;
      }
    }

    #endregion

    #region GroupDescriptions Property

    internal ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        return m_groupHandler.GroupDescriptions;
      }
    }

    #endregion

    #region Provider Property

    internal DataSourceProvider Provider
    {
      get
      {
        return m_dataSourceProvider;
      }
    }

    #endregion

    #region PrimaryKeyInfo Property

    internal IEnumerable<SortDescription> PrimaryKeyInfo
    {
      get
      {
        return m_dataSourceProvider.PrimaryKeyInfo;
      }
    }

    #endregion

    #region RequestExecutionDelay Property

    internal TimeSpan RequestExecutionDelay
    {
      get
      {
        return m_throttleHandler.RequestExecutionDelay;
      }
      set
      {
        m_throttleHandler.RequestExecutionDelay = value;
      }
    }

    #endregion

    #region CollapsedGroupCollections Property

    internal GroupLevelCollection CollapsedGroupCollections
    {
      get
      {
        return m_groupHandler.GroupCollections;
      }
    }

    #endregion

    #region CurrentSortOrder Property

    internal IEnumerable<SortDescription> CurrentSortOrder
    {
      get
      {
        return DataUtils.GetSortOrder(
          DataUtils.GetGroupNames( m_groupHandler.GroupDescriptions ),
          m_sortHandler.SortDescriptions,
          m_dataSourceProvider.PrimaryKeyInfo );
      }
    }

    #endregion

    #region IsPrimaryKeyVolatile Property

    internal bool IsPrimaryKeyVolatile
    {
      get
      {
        return m_dataSourceProvider.IsPrimaryKeyVolatile;
      }
    }

    #endregion

    #region SourceChanged Event

    internal event EventHandler<SourceChangedEventArgs> SourceChanged;

    private void OnSourceChanged( SourceChangedEventArgs e )
    {
      this.DisposeEnumerators();

      if( this.SourceChanged != null )
      {
        this.SourceChanged( this, e );
      }
    }

    #endregion

    #region ExpansionChanged Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanged;

    private void OnExpansionChanged( ExpansionChangedEventArgs args )
    {
      if( this.ExpansionChanged != null )
      {
        this.ExpansionChanged( this, args );
      }
    }

    #endregion

    private static DataSourceEnumeratorBase CreateDataSourceEnumerator(
      IAsyncEnumerator<DataPath> childEnumerator,
      GroupEnumerator groupEnumerator,
      DataSourceProvider dataSourceProvider,
      ReactiveDataHelper reactiveDataHelper,
      DataSourceDataHelper dataSourceDataHelper,
      GroupHandler groupHandler,
      ThrottlingHandler throttleHandler,
      bool isAsynchronousDataSource,
      bool enableCaching,
      bool requireSyncResult )
    {
      if( childEnumerator == null )
        throw new ArgumentNullException( "childEnumerator" );

      if( groupEnumerator == null )
        throw new ArgumentNullException( "groupEnumerator" );

      if( reactiveDataHelper == null )
        throw new ArgumentNullException( "reactiveDataHelper" );

      List<GroupCacheEnumeratorBase> groupCacheEnumerators = new List<GroupCacheEnumeratorBase>();
      DataSourceEnumeratorBase dataSourceEnumerator;

      if( requireSyncResult && ( isAsynchronousDataSource || !childEnumerator.IsFullySynchronized ) )
      {
        // This enumerator creates and caches dummy items for each pending data objects.
        ReactiveDataPathEnumerator reactiveEnumerator = new ReactiveDataPathEnumerator(
                                                          childEnumerator,
                                                          new DataPathFactory( reactiveDataHelper, groupHandler ),
                                                          dataSourceProvider,
                                                          reactiveDataHelper,
                                                          throttleHandler );
        ReactiveCacheEnumerator cacheEnumerator = new ReactiveCacheEnumerator(
                                                    reactiveEnumerator,
                                                    reactiveDataHelper,
                                                    DataSource.DefaultCacheSize );
        groupCacheEnumerators.Add( cacheEnumerator );

        // Cache lookup for GroupEnumerator.
        groupEnumerator.CachedPathProvider = cacheEnumerator.ProvideCachedPath;

        // This enumerator handles the item's position and manage inconsistencies
        // from the dummy items.
        dataSourceEnumerator = new StandardDataSourceEnumerator(
                                 cacheEnumerator,
                                 reactiveEnumerator,
                                 groupCacheEnumerators,
                                 dataSourceDataHelper,
                                 groupHandler );
      }
      else
      {
        IAsyncEnumerator<DataPath> newChildEnumerator;

        newChildEnumerator = new ContextEnumerator<DataPath>(
                               childEnumerator,
                               dataSourceProvider,
                               reactiveDataHelper );

        if( enableCaching )
        {
          GroupCacheEnumerator cacheEnumerator = new GroupCacheEnumerator(
                                                   newChildEnumerator,
                                                   reactiveDataHelper,
                                                   reactiveDataHelper,
                                                   reactiveDataHelper,
                                                   reactiveDataHelper,
                                                   DataSource.DefaultCacheSize );
          groupCacheEnumerators.Add( cacheEnumerator );

          newChildEnumerator = cacheEnumerator;
        }

        dataSourceEnumerator = new LocalDataSourceEnumerator(
                                 newChildEnumerator,
                                 groupCacheEnumerators,
                                 dataSourceDataHelper,
                                 groupHandler );
      }

      dataSourceEnumerator.EnableApproximateMoves = dataSourceProvider.IsAsynchronous;

      return dataSourceEnumerator;
    }

    private static IAsyncEnumerator<DataPath> CreateDataPathEnumerator(
      IEnumerable<SortDescription> currentCompleteSortOrder,
      DataSourceProvider dataSourceProvider,
      IContextualCompareHelper<RawDataItem> dataItemCompareHelper,
      IContextualItemHelper<RawDataItem> dataItemValueHelper,
      IContextualConvertHelper<object, RawDataItem> dataItemConvertHelper,
      IContextualConvertHelper<RawDataItem, DataPath> dataPathConverterHelper,
      IContextualGroupDataHelper groupHelper,
      SynchronizationContext synchronizationContext,
      out GroupEnumerator groupEnumerator )
    {
      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      // Make sure the DataSourceProvider supports the minimum that is required to
      // navigate.
      DataSource.CheckCapabilities( dataSourceProvider, currentCompleteSortOrder );

      // This enumerator converts a query from the JumpMoveAndFetch API to a query of
      // the QueryRequest API.
      IAsyncEnumerator<RawDataItem> objectEnumerator = new DataSourceProviderEnumerator<RawDataItem>( dataSourceProvider );

      if( synchronizationContext != null )
      {
        // This enumerator makes sure every operations is done synchronously.
        objectEnumerator = new FullySyncAsyncEnumerator<RawDataItem>( objectEnumerator );
      }

      // Set a few enumerators that will convert the queries based on the capabilities
      // of the DataSourceProvider.
      objectEnumerator = DataSource.CreateNavigationEnumerator(
                           currentCompleteSortOrder,
                           objectEnumerator,
                           dataSourceProvider,
                           dataItemCompareHelper,
                           dataItemValueHelper,
                           dataSourceProvider.CanSkip );

      // Set a paging enumerator that will slice one big fetch into many smaller fetches if
      // paging is enabled.
      objectEnumerator = DataSource.CreatePagingEnumerator( 
                           objectEnumerator, 
                           dataSourceProvider, 
                           dataItemValueHelper );


      objectEnumerator = DataSource.CreateFilteringEnumerator(
                           objectEnumerator,
                           dataSourceProvider,
                           dataItemConvertHelper,
                           dataItemCompareHelper );

      // This enumerator wraps each data object into a DataPath.
      IAsyncEnumerator<DataPath> dataPathEnumerator = new DataPathEnumerator( objectEnumerator, dataPathConverterHelper );

      // This enumerator handles any grouping functionality.
      groupEnumerator = new GroupEnumerator( dataPathEnumerator, groupHelper );
      dataPathEnumerator = groupEnumerator;

      if( synchronizationContext != null )
      {
        // This enumerator use a background thread to do the operations.
        dataPathEnumerator = new ConcurrentEnumerator( synchronizationContext, dataPathEnumerator );
      }

      return dataPathEnumerator;
    }

    private static IAsyncEnumerator<RawDataItem> CreateNavigationEnumerator(
      IEnumerable<SortDescription> currentCompleteSortOrder,
      IAsyncEnumerator<RawDataItem> childEnumerator,
      DataSourceProvider dataSourceProvider,
      IContextualCompareHelper<RawDataItem> compareHelper,
      IContextualItemHelper<RawDataItem> itemHelper,
      bool requiresOneWayNavigation )
    {
      IAsyncEnumerator<RawDataItem> objectEnumerator = childEnumerator;

      bool canJump = dataSourceProvider.CanJump;
      bool canJumpToItem = dataSourceProvider.CanJumpToItem;
      bool canJumpToDeletedItem = dataSourceProvider.CanJumpToDeletedItem;
      bool isNavigationFilterSupported = !( dataSourceProvider.IsPrimaryKeyVolatile )
                                       && ( NavigationFilterUtils.IsSupported( dataSourceProvider, currentCompleteSortOrder ) );
      bool addOneWayEnumerator = requiresOneWayNavigation;
      bool loop;

      do
      {
        loop = false;

        // We must set an enumerator that is looking for a SortPosition even if jumping or
        // filtering isn't supported since the [Deleted|Item]PositionConverterEnumerator may 
        // generate queries that use SortPosition.
        if( ( !canJump ) || ( !isNavigationFilterSupported ) )
        {
          objectEnumerator = new SortPositionFinderEnumerator<RawDataItem>( objectEnumerator, dataSourceProvider, compareHelper );
        }

        // Set an enumerator that is converting unusable ItemPosition into SortPosition.
        if( ( canJumpToItem ) && ( !canJumpToDeletedItem ) )
        {
          objectEnumerator = new DeletedItemPositionConverterEnumerator<RawDataItem>( objectEnumerator, itemHelper );
        }
        // Set an enumerator that is converting every ItemPosition into SortPosition.
        else if( !canJumpToItem )
        {
          objectEnumerator = new ItemPositionConverterEnumerator<RawDataItem>( objectEnumerator, itemHelper );
        }

        if( addOneWayEnumerator )
        {
          // This enumerator transform any bidirectional query into unidirectional queries.
          objectEnumerator = new OneWayEnumerator<RawDataItem>( objectEnumerator );

          // We need to loop once more to add the converters/finder enumerators prior
          // to the last added enumerator.
          loop = true;
          addOneWayEnumerator = false;
        }
      } while( loop );

      return objectEnumerator;
    }

    private static IAsyncEnumerator<RawDataItem> CreatePagingEnumerator(
      IAsyncEnumerator<RawDataItem> childEnumerator,
      DataSourceProvider dataSourceProvider,
      IContextualItemHelper<RawDataItem> itemHelper )
    {
      IAsyncEnumerator<RawDataItem> objectEnumerator = childEnumerator;
      Func<int> getPageSize = dataSourceProvider.GetPageSize;

      if( dataSourceProvider.CanSkip )
      {
        // This enumerator uses a skip operation to jump from one page to another.
        objectEnumerator = new SkipPagingEnumerator<RawDataItem>( objectEnumerator, getPageSize );
      }
      else
      {
        // This enumerator emulate a skip operation necessary to jump from one page to another
        // with a take operation.
        objectEnumerator = new TakePagingEnumerator<RawDataItem>( objectEnumerator, itemHelper, getPageSize );
      }

      return objectEnumerator;
    }

    private static IAsyncEnumerator<RawDataItem> CreateFilteringEnumerator(
      IAsyncEnumerator<RawDataItem> childEnumerator,
      DataSourceProvider dataSourceProvider,
      IContextualConvertHelper<object, RawDataItem> convertHelper,
      IContextualCompareHelper<RawDataItem> compareHelper )
    {
      IAsyncEnumerator<RawDataItem> objectEnumerator = childEnumerator;

      // Add an enumerator that will filter the data objects locally if it is necessary.
      objectEnumerator = new FilterEnumerator<RawDataItem>(
                           objectEnumerator,
                           dataSourceProvider,
                           convertHelper );

      return objectEnumerator;
    }

    private static void CheckCapabilities(
      DataSourceProvider dataSource,
      IEnumerable<SortDescription> sortOrder )
    {
      bool canSkip = dataSource.CanSkip;
      bool canJump = dataSource.CanJump;
      bool canJumpToItem = dataSource.CanJumpToItem;
      bool canFilter = NavigationFilterUtils.IsSupported( dataSource, sortOrder );
      bool isPrimaryKeyVolatile = dataSource.IsPrimaryKeyVolatile;

      // The DataSourceProvider doesn't allow navigation.
      if( !canSkip && !canJumpToItem && !canJump && !canFilter )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not provide navigation capabilities." );
      }
      // The DataSourceProvider allows item jumping which means ItemPosition are supported.
      // However, there is no way for us to find out the neighbors of an item that has been
      // removed from the data source after we've received a notification.
      else if( !canSkip && !canFilter )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not allow skipping and/or filtering." );
      }
      // The primary key used by the DataSourceProvider is volatile, which means it is
      // sensitive to the query's context.  For this kind of DataSourceProvider, jumping
      // is unreliable or unsupported while navigating with filters is forbidden due to
      // the volatile state of the primary key.  In other words, skipping is the only
      // usable way to navigate.
      else if( !canSkip && isPrimaryKeyVolatile )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not allow skipping." );
      }
    }

    private DataSourceEnumeratorBase CreateDataSourceEnumerator( bool requireSyncResult )
    {
      return ( DataSourceEnumeratorBase )this.CreateEnumerator( requireSyncResult, true );
    }

    private IAsyncEnumerator<DataPath> CreateEnumerator( bool requireSyncResult, bool requireDataSourceEnumerator )
    {
      bool isAsynchronousDataSource = m_dataSourceProvider.IsAsynchronous;
      DispatcherSynchronizationContext synchronizationContext = DataSource.CreateSynchronizationContext(
                                                                  m_dispatcher,
                                                                  isAsynchronousDataSource,
                                                                  requireSyncResult );
      IEnumerable<SortDescription> currentCompleteSortOrder = DataUtils.GetSortOrder(
                                                                DataUtils.GetGroupNames( m_groupHandler.GroupDescriptions ),
                                                                m_sortHandler.SortDescriptions,
                                                                m_dataSourceProvider.PrimaryKeyInfo );

      GroupEnumerator groupEnumerator;
      IAsyncEnumerator<DataPath> dataPathEnumerator = DataSource.CreateDataPathEnumerator(
                                                        currentCompleteSortOrder,
                                                        m_dataSourceProvider,
                                                        m_dataSourceProviderDataHelper,
                                                        m_dataSourceProviderDataHelper,
                                                        m_dataSourceProviderDataHelper,
                                                        m_dataPathDataHelper,
                                                        m_groupDataHelper,
                                                        synchronizationContext,
                                                        out groupEnumerator );

      bool enableCaching = ( ( isAsynchronousDataSource && requireSyncResult ) || m_dataSourceProvider.IsCachingAllowed );
      bool enableBuffering = ( enableCaching && m_dataSourceProvider.IsBufferingAllowed );
      IBufferConfigurable bufferManager = m_dataSourceProvider as IBufferConfigurable;

      // Add an enumerator that requests additional items to minimize the number of queries done
      // on a slow data source.
      if( enableBuffering && ( bufferManager != null ) )
      {
        dataPathEnumerator = GroupBufferingEnumerator.Create( 
                               dataPathEnumerator, 
                               bufferManager, 
                               m_bufferDataHelper, 
                               m_groupHandler );
      }

      if( !requireDataSourceEnumerator )
      {
        dataPathEnumerator = new ContextEnumerator<DataPath>(
                       dataPathEnumerator,
                       m_dataSourceProvider,
                       m_reactiveDataHelper );
        return dataPathEnumerator;
      }

      // Add an enumerator that will find out the number of items in the data source
      // and add it to every data query result.
      if( m_dataSourceProvider.CanCount )
      {
        dataPathEnumerator = new TotalCountEnumerator<DataPath>( dataPathEnumerator );
      }

      // Wraps an enumerator into a DataSourceEnumeratorBase.
      DataSourceEnumeratorBase dataSourceEnumerator = DataSource.CreateDataSourceEnumerator(
                                                        dataPathEnumerator,
                                                        groupEnumerator,
                                                        m_dataSourceProvider,
                                                        m_reactiveDataHelper,
                                                        m_dataSourceDataHelper,
                                                        m_groupHandler,
                                                        m_throttleHandler,
                                                        isAsynchronousDataSource,
                                                        enableCaching,
                                                        requireSyncResult );

      // Set a default item count for a potential scroll bar.
      Nullable<long> initialItemCount = null;

      if( m_dataSourceProvider is ICountRestrictedProvider )
      {
        initialItemCount = ( ( ICountRestrictedProvider )m_dataSourceProvider ).InitialItemCount;
      }

      if( initialItemCount != null )
      {
        dataSourceEnumerator.SetCountValues( null, new EstimateItemCount( initialItemCount.Value, true, false ) );
      }
      else
      {
        dataSourceEnumerator.SetCountValues( null, new EstimateItemCount( DataSource.SystemDefaultItemCount, true, true ) );
      }

      return dataSourceEnumerator;
    }

    private void InternalSourceChanged( object sender, SourceChangedEventArgs args )
    {
      this.OnSourceChanged( args );
    }

    private void GroupCollapseChanged( object sender, ExpansionChangedEventArgs args )
    {
      this.OnExpansionChanged( args );
    }

    private void RegisterDisposableEnumerator( IDisposable enumerator )
    {
      if( enumerator != null )
      {
        m_activeEnumerators.Add( new WeakReference( enumerator ) );
      }
    }

    private void DisposeEnumerators()
    {
      foreach( WeakReference enumRef in m_activeEnumerators )
      {
        IDisposable enumerator = enumRef.Target as IDisposable;
        if( enumerator != null )
        {
          enumerator.Dispose();
        }
      }

      m_activeEnumerators.Clear();
    }

    internal object GetDataObjectPropertyValue( string propertyName, DataItem item )
    {
      RawDataItem dataItem = item as RawDataItem;

      return m_dataSourceProviderDataHelper.GetMemberValue( null, propertyName, dataItem );
    }

    internal ISyncDataSourceEnumerator GetSyncDataEnumerator()
    {
      DataSourceEnumeratorBase asyncEnumerator = this.CreateDataSourceEnumerator( true );

      asyncEnumerator.PreventEstimateIndex = true;
      asyncEnumerator.ProviderSupportGroupCount = m_dataSourceProvider.CanGroupCount;

      ISyncDataSourceEnumerator enumerator = this.GetSyncDataEnumerator( asyncEnumerator );


      this.RegisterDisposableEnumerator( enumerator );

      return enumerator;
    }

    internal IPeekableEnumerator<DataPath> GetEnumerator()
    {
      IPeekableEnumerator<DataPath> asyncEnumerator = ( IPeekableEnumerator<DataPath> )this.CreateEnumerator( false, false );


      this.RegisterDisposableEnumerator( asyncEnumerator );

      return asyncEnumerator;
    }

    internal ISyncDataSourceEnumerator GetSyncDataEnumerator( DataSourceEnumeratorBase asyncEnumerator )
    {
      if( asyncEnumerator is ICachingEnumerator )
      {
        return new SyncCachingDataSourceEnumerator( asyncEnumerator );
      }
      else
      {
        return new SyncDataSourceEnumerator( asyncEnumerator );
      }
    }

    internal ISyncDataSourceEnumerator GetSyncDebugLogDataSourceEnumerator( ISyncDataSourceEnumerator enumerator )
    {
      if( enumerator is ICachingEnumerator )
      {
        return new SyncCachingDebugLogDataSourceEnumerator( enumerator );
      }
      else
      {
        return new SyncDebugLogDataSourceEnumerator( enumerator );
      }
    }

    internal IAsyncDataSourceEnumerator GetAsyncDataEnumerator()
    {
      DataSourceEnumeratorBase enumerator = this.CreateDataSourceEnumerator( false );

      this.RegisterDisposableEnumerator( enumerator );

      return enumerator;
    }

    internal DataRangeContext GetCurrentDataRangeContext()
    {
      return new DataRangeContext(
        m_dataSourceProvider.ElementType,
        this.CurrentSortOrder,
        this.Filter,
        this.FilterExpression );
    }

    internal IDataRangeEnumerator GetDataRangeEnumerator( IEnumerable<DataRange> ranges )
    {
      return this.GetDataRangeEnumerator(
        ranges,
        Enumerable.Empty<GroupDescription>(),
        Enumerable.Empty<SortDescription>() );
    }

    internal IDataRangeEnumerator GetDataRangeEnumerator(
      IEnumerable<DataRange> ranges,
      IEnumerable<GroupDescription> groupDescriptions,
      IEnumerable<SortDescription> sortDescriptions )
    {
      ranges = this.OptimizeDataRanges( ranges );

      // If no range is specified, then no data will be returned.
      if( ranges == null )
      {
        ranges = Enumerable.Empty<DataRange>();
      }

      Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> enumeratorCreator = DataSource.GetDataPathEnumeratorCreator( m_dispatcher );
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> dataPathEnumeratorCreator =
          ( groupHandler, sortHandler, filterHandler ) =>
            enumeratorCreator.Invoke(
              m_dataSourceProvider,
              groupHandler,
              sortHandler,
              filterHandler );

      // Find out if the data source has a query length limitation.
      bool isQueryLengthLimited = m_dataSourceProvider.IsQueryLengthLimited;

      // Find out if the data source handles filter expressions that are
      // use for navigation purpose.
      IEnumerable<string> sortedPropertiesNames =
        groupDescriptions.Select( ( gd ) => gd.GetPropertyName() )
                         .Concat( sortDescriptions.Select( ( sd ) => sd.PropertyName ) )
                         .Concat( m_dataSourceProvider.PrimaryKeyInfo.Select( ( sd ) => sd.PropertyName ) );
      bool isNavigationFilterSupported = NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortedPropertiesNames );

      // To use a GroupDataRangeEnumerator, the underlying data source must
      // be able to apply filter expressions that will bound the desired data.
      // Since the filter expressions can be huge, the data source must not
      // have an query length limitation.
      bool getAllDataItemsBeforeGrouping = ( isQueryLengthLimited )
                                        || ( !isNavigationFilterSupported );

      // We will need to download all the items that are within the range before grouping
      // and sorting them locally.
      IDataRangeEnumerator dataRangeEnumerator;
      if( getAllDataItemsBeforeGrouping )
      {
        dataRangeEnumerator = new LocalGroupDataRangeEnumerator(
                                ranges,
                                groupDescriptions,
                                sortDescriptions,
                                m_dataSourceProvider.PrimaryKeyInfo,
                                m_dataSourceProvider.ElementType,
                                dataPathEnumeratorCreator,
                                enumeratorCreator,
                                m_dispatcher );
      }
      // The remote data source can handle the grouping and the sorting for the ranges.
      else
      {
        dataRangeEnumerator = new GroupDataRangeEnumerator(
                                ranges,
                                groupDescriptions,
                                sortDescriptions,
                                m_dataSourceProvider.PrimaryKeyInfo,
                                m_dataSourceProvider.ElementType,
                                m_dataSourceProvider.GetElementMemberType,
                                dataPathEnumeratorCreator );
      }

      return dataRangeEnumerator;
    }

    private IEnumerable<DataRange> OptimizeDataRanges( IEnumerable<DataRange> ranges )
    {
      if( ranges == null )
        return Enumerable.Empty<DataRange>();

      List<DataRange> dataRanges = ranges.ToList();

      DataSource.RemoveOlderDuplicateRange( dataRanges );
      DataSource.RemoveStartingUnselectionRange( dataRanges );

      return dataRanges;
    }

    private static void RemoveStartingUnselectionRange( IList<DataRange> ranges )
    {
      while( ranges.Count > 0 )
      {
        if( ranges.First().IsSelectionRangeInclusive )
          break;

        // Unselection range found at the beginning of the list has no impact
        // on the final result.
        ranges.RemoveAt( 0 );
      }
    }

    private static void RemoveOlderDuplicateRange( IList<DataRange> ranges )
    {
      for( int skip = 0; skip < ranges.Count; skip++ )
      {
        int index = ranges.Count - skip - 1;
        DataRange current = ranges[ index ];

        for( int i = index - 1; i >= 0; i-- )
        {
          // For ranges that targets the same interval, only the newest range
          // is necessary.
          if( current.IsSameRange( ranges[ i ] ) )
          {
            ranges.RemoveAt( i );
          }
        }
      }
    }

    internal void InvalidateData()
    {
      this.DisposeEnumerators();

      if( m_dataSourceProvider != null )
      {
        m_dataSourceProvider.InvalidateData();
      }
    }

    internal void InvalidateDataPath( DataPath dataPath )
    {
      if( m_dataSourceDataHelper != null )
      {
        m_dataSourceDataHelper.Invalidate( dataPath );
      }
    }

    internal IEnumerable<FilterOperator> GetSupportedFilterOperators( Type dataType )
    {
      return m_dataSourceProvider.SupportedFilterOperators( dataType );
    }

    private static Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> GetDataPathEnumeratorCreator( Dispatcher dispatcher )
    {
      return ( dataSourceProvider, groupHandler, sortHandler, filterHandler ) =>
        {
          bool isThreadingEnabled = DataSource.IsThreadingEnabled( dataSourceProvider.IsAsynchronous, false );
          DispatcherSynchronizationContext synchronizationContext = ( isThreadingEnabled )
                                                                      ? new DispatcherSynchronizationContext( dispatcher )
                                                                      : null;

          DataSourceProviderDataHelper dataSourceProviderDataHelper = new DataSourceProviderDataHelper( dataSourceProvider );
          DataPathDataHelper<object> dataPathDataHelper = new DataPathDataHelper<object>( dataSourceProviderDataHelper );
          GroupDataHelper groupDataHelper = new GroupDataHelper( new DataPathDataHelper( dataPathDataHelper ) );
          IEnumerable<SortDescription> currentCompleteSortOrder = DataUtils.GetSortOrder(
                                                                    DataUtils.GetGroupNames( groupHandler.GroupDescriptions ),
                                                                    sortHandler.SortDescriptions,
                                                                    dataSourceProvider.PrimaryKeyInfo );

          GroupEnumerator groupEnumerator;
          IAsyncEnumerator<DataPath> dataPathEnumerator = DataSource.CreateDataPathEnumerator(
                                                            currentCompleteSortOrder,
                                                            dataSourceProvider,
                                                            dataSourceProviderDataHelper,
                                                            dataSourceProviderDataHelper,
                                                            dataSourceProviderDataHelper,
                                                            dataPathDataHelper,
                                                            groupDataHelper,
                                                            synchronizationContext,
                                                            out groupEnumerator );

          bool isAsynchronousDataSource = dataSourceProvider.IsAsynchronous;
          bool isCachingEnabled = ( isAsynchronousDataSource || dataSourceProvider.IsCachingAllowed );
          bool isBufferingEnabled = ( isCachingEnabled && dataSourceProvider.IsBufferingAllowed );
          IBufferConfigurable bufferManager = dataSourceProvider as IBufferConfigurable;
          BufferDataHelper dataHelper = new BufferDataHelper( groupDataHelper, groupHandler, sortHandler, filterHandler, dataSourceProvider.PrimaryKeyInfo );

          // This enumerator requests additional items to minimize the number of queries done
          // on a slow data source.
          if( isBufferingEnabled && ( bufferManager != null ) )
          {
            dataPathEnumerator = GroupBufferingEnumerator.Create(
                                   dataPathEnumerator,
                                   bufferManager,
                                   dataHelper,
                                   groupHandler );
          }

          dataPathEnumerator = new ContextEnumerator<DataPath>(
                                 dataPathEnumerator,
                                 dataSourceProvider,
                                 dataHelper );

          return dataPathEnumerator;
        };
    }

    private static bool IsThreadingEnabled( bool isAsynchronousDataSource, bool requireSyncResult )
    {
      return false;
    }

    private static DispatcherSynchronizationContext CreateSynchronizationContext(
      Dispatcher dispatcher,
      bool isAsynchronousDataSource,
      bool requireSyncResult )
    {
      if( !DataSource.IsThreadingEnabled( isAsynchronousDataSource, requireSyncResult ) )
        return null;

      if( dispatcher == null )
        throw new ArgumentNullException( "dispatcher" );

      return new DispatcherSynchronizationContext( dispatcher );
    }

    #region IDataEnumerable<DataPath> Members

    IAsyncEnumerator<DataPath> IDataEnumerable<DataPath>.GetAsyncDataEnumerator()
    {
      return this.GetAsyncDataEnumerator();
    }

    #endregion

    #region Private Fields

    private readonly Dispatcher m_dispatcher;
    private readonly DataSourceProvider m_dataSourceProvider;
    private readonly List<WeakReference> m_activeEnumerators = new List<WeakReference>();

    private readonly GroupHandler m_groupHandler = new GroupHandler();
    private readonly SortingHandler m_sortHandler = new SortingHandler();
    private readonly FilteringHandler m_filterHandler = new FilteringHandler();
    private readonly ThrottlingHandler m_throttleHandler = new ThrottlingHandler();

    private readonly DataSourceProviderDataHelper m_dataSourceProviderDataHelper;
    private readonly DataPathDataHelper<object> m_dataPathDataHelper;
    private readonly GroupDataHelper m_groupDataHelper;
    private readonly BufferDataHelper m_bufferDataHelper;
    private readonly ReactiveDataHelper m_reactiveDataHelper;
    private readonly DataSourceDataHelper m_dataSourceDataHelper;

    #endregion
  }
}
